/*
 * Copyright 2006-2008 Michele Mostarda ( michele.mostarda@gmail.com ).
 * All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package org.graphnav.model;

import org.graphnav.model.event.GraphModelEvent;
import org.graphnav.model.event.GraphModelListener;
import org.graphnav.swing.Arc;
import org.graphnav.swing.DefaultArc;
import org.graphnav.swing.DefaultNode;
import org.graphnav.swing.Node;

import java.awt.Point;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * Provides a default implementation of a <code>ModifiableGraphModel</code>
 *
 * @see org.graphnav.model.ModifiableGraphModel
 */
public class DefaultGraphModel implements ModifiableGraphModel {

    /**
     * List of nodes present in the model.
     */
    protected List<Node> nodes;

    /**
     * List of arcs present in the model.
     */
    protected List<Arc> arcs;

    /**
     * List of listeners of the model.
     */
    private List<GraphModelListener> listeners;

    /**
     * Contructor.
     */
    public DefaultGraphModel() {
        nodes     = new ArrayList<Node>();
        arcs      = new ArrayList<Arc>();
        listeners = new LinkedList<GraphModelListener>();
    }

    public void startVisit() {
        //EMPTY.
    }

    public void endVisit() {
        //EMPTY.
    }

    public Iterator<Node> nodes() {
        return nodes.iterator();
    }

    public Iterator<Arc> arcs() {
        return arcs.iterator();
    }

    public Node getNode(int i) {
        return nodes.get(i);
    }

    public Arc getArc(int i) {
        return arcs.get(i);
    }

    public int nodesCount() {
        return nodes.size();
    }

    public int arcsCount() {
       return arcs.size();
    }

    /**
     *   @see org.graphnav.model.GraphModel#getOutgoingNodes
     */
    public Iterator<Node> getOutgoingNodes(final Node n) {
        return new Iterator<Node>() {

            private Iterator<Arc> internal = arcs();
            private Node nextNode = null;

            public boolean hasNext() {
                Arc current;
                while(nextNode == null && internal.hasNext()) {
                    current = internal.next();
                    if(current.getFromNode().equals(n)) {
                        nextNode = current.getToNode();
                    }
                }
                return nextNode != null;
            }

            public Node next() {
                if( hasNext() ) {
                    Node tmp = nextNode;
                    nextNode = null;
                    return tmp;
                }
                throw new NoSuchElementException();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    /**
     * @see org.graphnav.model.GraphModel#getIngoingNodes(org.graphnav.swing.Node)
     */
    public Iterator<Node> getIngoingNodes(final Node n) {
        return new Iterator<Node>() {

            private Iterator internal = arcs();
            private Node nextNode = null;

            public boolean hasNext() {
                Arc current;
                while(nextNode == null && internal.hasNext()) {
                    current = (Arc) internal.next();
                    if(current.getToNode().equals(n)) {
                        nextNode = current.getFromNode();
                    }
                }
                return nextNode != null;
            }

            public Node next() {
                if( hasNext() ) {
                    Node tmp = nextNode;
                    nextNode = null;
                    return tmp;
                }
                throw new NoSuchElementException();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    /**
     * @see org.graphnav.model.GraphModel#outgoingNodesCount(org.graphnav.swing.Node)  
     */
    public int outgoingNodesCount(Node n) {
        int result = 0;
        Iterator arcsIter = arcs();
        while(arcsIter.hasNext()) {
            if( ( (Arc) arcsIter.next() ).getFromNode().equals(n) ) {
                result++;
            }
        }
        return result;
    }

    /**
     * @see org.graphnav.model.GraphModel#ingoingNodesCount(org.graphnav.swing.Node) 
     */
    public int ingoingNodesCount(Node n) {
        int result = 0;
        Iterator arcsIter = arcs();
        while(arcsIter.hasNext()) {
            if( ( (Arc) arcsIter.next() ).getToNode().equals(n) ) {
                result++;
            }
        }
        return result;
    }

    /**
     * TODO: optimize this.
     * @see org.graphnav.model.GraphModel#adiacent(org.graphnav.swing.Node, org.graphnav.swing.Node) 
     */
    public boolean adiacent(Node n1, Node n2) {
        Arc current;
        for( Iterator arcsIter = arcs(); arcsIter.hasNext(); ) {
            current = (Arc) arcsIter.next();
            if(
                    ( current.getFromNode().equals(n1) && current.getToNode().equals(n2) )
                        ||
                    ( current.getFromNode().equals(n2) && current.getToNode().equals(n1) )
            ) {
                return true;
            }
        }
        return false;
    }

    /**
     * @see org.graphnav.model.GraphModel#addGraphModelListener(org.graphnav.model.event.GraphModelListener)  
     */
    public void addGraphModelListener(GraphModelListener gml) {
        listeners.add(gml);
    }

    /**
     * @see org.graphnav.model.GraphModel#removeGraphModelListener(org.graphnav.model.event.GraphModelListener)  
     */

    public void removeGraphModelListener(GraphModelListener gml) {
        listeners.remove(gml);
    }

    /**
     * @see ModifiableGraphModel#addNode(org.graphnav.swing.Node)
     */
    public void addNode(Node n) {
        nodes.add(n);
        notifyModelChanged();
    }

    /**
     * @see org.graphnav.model.ModifiableGraphModel@removeNode
     */
    public void removeNode(Node n) {
        Iterator arcs = arcs();
        Arc a = null;
        while(arcs.hasNext()) {
            a = (Arc) arcs.next();
        }
        if( n.equals(a.getFromNode()) || n.equals(a.getToNode()) ) {
            arcs.remove();
        }
        nodes.remove(n);
        notifyModelChanged();
    }

    public Arc addArc(Node fromNode, Node toNode, Object data) {
        DefaultArc arc = new DefaultArc( data == null ? null : data.toString(), fromNode, toNode);
        addArc(arc);
        return arc;
    }

    public void addArc(Arc arc) {
        arcs.add(arc);
        notifyModelChanged();
    }

    public void removeArc(Arc arc) {
        arcs.remove(arc);
        notifyModelChanged();
    }

    public Node createNode(Object data, Point position) {
        return new DefaultNode(data.toString(), position);
    }

    public Arc createArc(Node source, Node dest, Object data) {
        return new DefaultArc(data.toString(), source, dest);
    }

    /**
     * Cleanup the model removing all nodes and arcs.
     */
    public void clear() {
        nodes.clear();
        arcs .clear();
        notifyModelChanged();
    }

    /**
     * Notifies to all listeners that the model was changed.
     */
    protected void notifyModelChanged() {
        final GraphModelEvent gme = new GraphModelEvent(this);
        for (GraphModelListener listener : listeners) {
            (listener).modelChanged(gme);
        }
    }

}
